﻿#region Copyright & licence

// This file is part of Refix.
// 
// Refix is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// Refix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with Refix.  If not, see <http://www.gnu.org/licenses/>.
// 
// Copyright (C) 2010-11 David Musgrove and others.

#endregion

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

using Refix.Core.IO;
using Refix.Core.Options;
using Refix.Core.Properties;

namespace Refix.Core.Commands
{
    public class Solution : SingleSolutionCommand
    {
        private const string PREBUILD_PROJECT_FILE = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
  <PropertyGroup>
    <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
    <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
    <ProjectGuid>{{6BEF7D6C-B306-4656-9902-732C5F3E3DC1}}</ProjectGuid>
    <OutputPath>.</OutputPath>
    <FileUpgradeFlags></FileUpgradeFlags>
    <OldToolsVersion>3.5</OldToolsVersion>
    <UpgradeBackupLocation />
  </PropertyGroup>
  <Import Project=""RefixMSBuild.targets"" />
  <Target Name=""Clean"">
  </Target>
  <Target Name=""Build"">
    <Message Text=""Refix prebuild"" />
    <PrebuildTask SolutionFile=""{0}"" Verbosity=""Verbose"" />
  </Target>
  <Target Name=""Rebuild"">
    <Message Text=""Refix prebuild"" />
    <PrebuildTask SolutionFile=""{0}"" Verbosity=""Verbose"" />
  </Target>
</Project>";

        private const string POSTBUILD_PROJECT_FILE = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
  <PropertyGroup>
    <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
    <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
    <ProjectGuid>{{EBD65183-255F-478B-8526-EEA89851F5BA}}</ProjectGuid>
    <OutputPath>.</OutputPath>
    <FileUpgradeFlags></FileUpgradeFlags>
    <OldToolsVersion>3.5</OldToolsVersion>
    <UpgradeBackupLocation />
  </PropertyGroup>
  <Import Project=""RefixMSBuild.targets"" />
  <Target Name=""Clean"">
  </Target>
  <Target Name=""Build"">
    <Message Text=""Refix postbuild"" />
    <PostbuildTask SolutionFile=""{0}"" Verbosity=""Verbose"" BuildConfiguration=""$(Configuration)"" />
  </Target>
  <Target Name=""Rebuild"">
    <Message Text=""Refix postbuild"" />
    <PostbuildTask SolutionFile=""{0}"" Verbosity=""Verbose"" BuildConfiguration=""$(Configuration)"" />
  </Target>
</Project>";

        static private readonly string[] _solutionFolderLines = new[]
                                                                {
                                                                    @"Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Refix"", ""Refix"", ""{3510E404-EFBE-4DDC-80B8-AF20D2FF3CDC}""",
                                                                    "EndProject"
                                                                };

        static private readonly string[] _prebuildProjectLines = new[]
                                                                 {
                                                                     @"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""Prebuild"", ""Refix\{0}.Prebuild.csproj"", ""{{6BEF7D6C-B306-4656-9902-732C5F3E3DC1}}""",
                                                                     "EndProject"
                                                                 };

        static private readonly string[] _postbuildProjectLines = new[]
                                                                  {
                                                                      @"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""Postbuild"", ""Refix\{0}.Postbuild.csproj"", ""{{EBD65183-255F-478B-8526-EEA89851F5BA}}""",
                                                                      "	ProjectSection(ProjectDependencies) = postProject",
                                                                      "	EndProjectSection",
                                                                      "EndProject"
                                                                  };

        static private readonly string[] _projectDependenciesLines = new[]
                                                                  {
                                                                      "	ProjectSection(ProjectDependencies) = postProject",
                                                                      "	EndProjectSection"
                                                                  };

        static private readonly string[] _nestedProjectsLines = new[]
                                                                {
                                                                    "	GlobalSection(NestedProjects) = preSolution",
                                                                    "	EndGlobalSection"
                                                                };

        static private readonly string[] _projectConfigurationLines = new[]
                                                                {
                                                                    "	GlobalSection(ProjectConfigurationPlatforms) = postSolution",
                                                                    "	EndGlobalSection"
                                                                };

        static private bool _postbuildAdded;
        static private bool _prebuildAdded;

        protected override string HelpText
        {
            get { return ResourceDispenser.GetString("SOLUTION_HELP"); }
        }

        private bool IsOutputSpecified
        {
            get { return Options.Options.Any(o => o is Output)
                && !string.Equals(OutputFileName, FileName, StringComparison.OrdinalIgnoreCase); }
        }

        private string OutputFileName
        {
            get { return Path.Combine(Directory.GetCurrentDirectory(), ((Output)Options.Options.First(o => o is Output)).FileName); }
        }

        public override bool Execute()
        {
            if (!IsOutputSpecified && File.Exists(FileName + FileExtensions.BackupFile))
            {
                OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("SOLUTION_EXISTING_BACKUP_FILE"));
                return false;
            }

            var solutionFile = new SolutionFile(FileName);
            int lines = solutionFile.FileLines.Count;
            if (!IsOutputSpecified)
            {
                File.WriteAllLines(FileName + FileExtensions.BackupFile, solutionFile.FileLines.ToArray(), Encoding.UTF8);
            }

            int nestedProjectsLine = AddNestedProjectsIfNecessary(solutionFile);
            InsertNestedProjectIfNecessary(solutionFile, nestedProjectsLine, ProjectGuids.PrebuildProject);
            InsertNestedProjectIfNecessary(solutionFile, nestedProjectsLine, ProjectGuids.PostbuildProject);

            AddProjectsAndSolutionFolderIfNecessary(solutionFile);
            int postbuildProjectStart =
                solutionFile.FileLines.IndexOf(string.Format(_postbuildProjectLines[0],
                                                             Path.GetFileNameWithoutExtension(solutionFile.FileName)));
            foreach (var projectGuid in solutionFile.CachedProjectFiles.Keys)
            {
                if (projectGuid == ProjectGuids.PrebuildProject) continue;
                if (projectGuid == ProjectGuids.PostbuildProject) continue;
                InsertDependencyForPostBuildIfNecessary(solutionFile, postbuildProjectStart, projectGuid);
            }
            foreach (var projectGuid in solutionFile.CachedProjectFiles.Keys)
            {
                if (projectGuid == ProjectGuids.PrebuildProject) continue;
                if (projectGuid == ProjectGuids.PostbuildProject) continue;
                InsertDependencyOnPreBuildIfNecessary(solutionFile, projectGuid);
            }
            int projectConfigurationLine = solutionFile.FileLines.IndexOf(_projectConfigurationLines[0]);
            int projectConfigurationLineEnd = solutionFile.FileLines.IndexOf(_projectConfigurationLines[1],
                                                                             projectConfigurationLine);
            if (_prebuildAdded)
            {
                AddProjectConfigurations(solutionFile, projectConfigurationLineEnd, ProjectGuids.PrebuildProject);
            }
            if (_postbuildAdded)
            {
                AddProjectConfigurations(solutionFile, projectConfigurationLineEnd, ProjectGuids.PostbuildProject);
            }
            if (_prebuildAdded || _postbuildAdded)
            {
                string sourceDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                string targetDirectory = Path.Combine(Path.GetDirectoryName(FileName), "Refix");
                CopyFileIfNotExists("Refix.MSBuild.dll", sourceDirectory, targetDirectory);
                CopyFileIfNotExists("Refix.Core.dll", sourceDirectory, targetDirectory);
                CopyFileIfNotExists("RefixMSBuild.targets", sourceDirectory, targetDirectory);
                CopyFileIfNotExists("ICSharpCode.SharpZipLib.dll", sourceDirectory, targetDirectory);
            }

            if (solutionFile.FileLines.Count == lines && !IsOutputSpecified)
            {
                File.Delete(FileName + FileExtensions.BackupFile);
            }
            else
            {
                string fileName = IsOutputSpecified ? OutputFileName : FileName;
                File.WriteAllLines(fileName, solutionFile.FileLines.ToArray(), Encoding.UTF8);
            }
            return true;
        }

        static private void CopyFileIfNotExists(string fileName, string sourceDirectory, string targetDirectory)
        {
            if (!File.Exists(Path.Combine(targetDirectory, fileName)))
            {
                File.Copy(Path.Combine(sourceDirectory, fileName), Path.Combine(targetDirectory, fileName));
            }
        }

        static private void AddProjectConfigurations(SolutionFile solutionFile, int projectConfigurationLineEnd, Guid projectGuid)
        {
            string guidToInsert = projectGuid.ToString("B").ToUpperInvariant();
            AddConfigurationLinesForConfiguration(solutionFile, projectConfigurationLineEnd, guidToInsert, "Release");
            AddConfigurationLinesForConfiguration(solutionFile, projectConfigurationLineEnd, guidToInsert, "Debug");
        }

        static private void AddConfigurationLinesForConfiguration(SolutionFile solutionFile, int projectConfigurationLineEnd, string guidToInsert, string configuration) {
            solutionFile.FileLines.Insert(projectConfigurationLineEnd,
                                          string.Format("\t\t{0}.{1}|Any CPU.Build.0 = Debug|Any CPU", guidToInsert, configuration));
            solutionFile.FileLines.Insert(projectConfigurationLineEnd,
                                          string.Format("\t\t{0}.{1}|Any CPU.ActiveCfg = Debug|Any CPU", guidToInsert, configuration));
        }

        static private void InsertNestedProjectIfNecessary(SolutionFile solutionFile, int nestedProjectsLine, Guid projectGuid)
        {
            int nestedProjectsLineEnd = solutionFile.FileLines.IndexOf(_nestedProjectsLines[1], nestedProjectsLine);
            string guidToMatch = projectGuid.ToString("B").ToUpperInvariant();
            bool found = false;
            for (int i = nestedProjectsLine + 1; i < nestedProjectsLineEnd; i++)
            {
                if (solutionFile.FileLines[i].Contains(guidToMatch))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                solutionFile.FileLines.Insert(nestedProjectsLineEnd, string.Format("\t\t{0} = {{3510E404-EFBE-4DDC-80B8-AF20D2FF3CDC}}", guidToMatch));
            }
        }

        static private void InsertDependencyOnPreBuildIfNecessary(SolutionFile solutionFile, Guid projectGuid)
        {
            int projectLine = 0;
            string guidToMatch = projectGuid.ToString("B").ToUpperInvariant();
            while (!solutionFile.FileLines[++projectLine].Contains(guidToMatch)) {}
            if (solutionFile.FileLines[projectLine + 1] != _projectDependenciesLines[0])
            {
                solutionFile.FileLines.InsertRange(projectLine + 1, _projectDependenciesLines);
            }
            int projectLineEnd = solutionFile.FileLines.IndexOf(_projectDependenciesLines[1], projectLine);
            guidToMatch = ProjectGuids.PrebuildProject.ToString("B").ToUpperInvariant();
            bool found = false;
            for (int i = projectLine + 1; i < projectLineEnd; i++)
            {
                if (solutionFile.FileLines[i].Contains(guidToMatch))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                solutionFile.FileLines.Insert(projectLineEnd, string.Format("\t\t{0} = {0}", guidToMatch));
            }
        }

        static private void InsertDependencyForPostBuildIfNecessary(SolutionFile solutionFile, int postbuildProjectLine, Guid projectGuid)
        {
            int postbuildProjectLineEnd = solutionFile.FileLines.IndexOf(_postbuildProjectLines[2], postbuildProjectLine);
            string guidToMatch = projectGuid.ToString("B").ToUpperInvariant();
            bool found = false;
            for (int i = postbuildProjectLine + 1; i < postbuildProjectLineEnd; i++)
            {
                if (solutionFile.FileLines[i].Contains(guidToMatch))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                solutionFile.FileLines.Insert(postbuildProjectLineEnd, string.Format("\t\t{0} = {0}", guidToMatch));
            }
        }

        static private int AddNestedProjectsIfNecessary(SolutionFile solutionFile)
        {
            int index = solutionFile.FileLines.IndexOf(_nestedProjectsLines[0]);
            if (index < 0)
            {
                index = solutionFile.FileLines.IndexOf("EndGlobal");
                solutionFile.FileLines.InsertRange(index, _nestedProjectsLines);
            }
            return index;
        }

        static private void AddProjectsAndSolutionFolderIfNecessary(SolutionFile solutionFile)
        {
            int globalSectionStart = solutionFile.FileLines.IndexOf("Global");
            string solutionFileRoot = Path.GetFileNameWithoutExtension(solutionFile.FileName);
            if (!solutionFile.SolutionFolders.Contains("Refix"))
            {
                solutionFile.FileLines.InsertRange(globalSectionStart, _solutionFolderLines);
            }
            if (!solutionFile.CachedProjectFiles.ContainsKey(ProjectGuids.PrebuildProject))
            {
                _prebuildAdded = true;
                SaveRefixProjectFile(solutionFile, ".Prebuild.csproj", PREBUILD_PROJECT_FILE);
                solutionFile.FileLines.Insert(globalSectionStart, _prebuildProjectLines[1]);
                solutionFile.FileLines.Insert(globalSectionStart, string.Format(_prebuildProjectLines[0], solutionFileRoot));
            }
            if (!solutionFile.CachedProjectFiles.ContainsKey(ProjectGuids.PostbuildProject))
            {
                _postbuildAdded = true;
                SaveRefixProjectFile(solutionFile, ".Postbuild.csproj", POSTBUILD_PROJECT_FILE);
                solutionFile.FileLines.Insert(globalSectionStart, _postbuildProjectLines[3]);
                solutionFile.FileLines.Insert(globalSectionStart, _postbuildProjectLines[2]);
                solutionFile.FileLines.Insert(globalSectionStart, _postbuildProjectLines[1]);
                solutionFile.FileLines.Insert(globalSectionStart, string.Format(_postbuildProjectLines[0], solutionFileRoot));
            }
        }

        static private void SaveRefixProjectFile(SolutionFile solutionFile, string suffix, string template) {
            string directoryName = Path.Combine(Path.GetDirectoryName(solutionFile.FileName), "Refix");
            if (!Directory.Exists(directoryName))
            {
                Directory.CreateDirectory(directoryName);
            }
            string fileName = Path.GetFileNameWithoutExtension(solutionFile.FileName) + suffix;
            fileName = Path.Combine(directoryName, fileName);
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }
            string relativePathToSolution = PathUtilities.MakeRelativePath(fileName, solutionFile.FileName);
            File.WriteAllText(fileName, string.Format(template, relativePathToSolution));
        }
    }
}